home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / mmdf / mmdfq < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  66.8 KB  |  1,926 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) mmdfq.gawk 2.4 97/05/27
  4. # 92/09/21 john h. dubois iii (john@armory.com)
  5. # 92/09/28 Converted to #!gawk script.  Added options and command line args.
  6. # 93/12/27 Added conmtaAdDrRiksMS options, assorted bugfixes
  7. # 94/05/10 Make stat exit 0 for gawk
  8. # 95/03/16 Added ( and ) to the ^ and $ used to anchor patterns, so that e.g.
  9. #          addr1|addr2 will work as expected.
  10. # 96/01/20 Make tempfile name begin with #
  11. # 96/02/09 Added x option and checking for correct contents of address files.
  12. # 96/06/03 Don't try to get message file sizes if there are none.
  13. #          Complain about empty address files explicitly.
  14. # 96/10/14 Use termtype for default width.  Renamed some header fields so that
  15. #          they match option letters that select by that field.
  16. #          Cleaned up hostname canonicalization.  Added bpq options.
  17. # 96/12/06 Print control chars in msg file in symbolic form in error message.
  18. # 97/02/09 Replaced ADR options with leading ! in value for adr options.
  19. # 97/05/27 Changed NO_ADDR to !ADDR so output lines up in mmdfsel.
  20. #          Print - for flags if no address file.  Let E & M be given together.
  21. #          Added O option.  Made -t work correctly.
  22. #          Made -p only print paths to whichever of addr/msg files exist.
  23.  
  24. # Uses gawk for strftime() and systime()
  25.  
  26. BEGIN {
  27.     Name = "mmdfq"
  28.     Usage = \
  29. "Usage:\n"\
  30. Name " [-AehiklnoOpqsVME] [-w<width>] [-c<chan>[,<chan]>] [-t<time>]\n"\
  31. "      [-a<address>] [-d<host>] [-r<return-address>] [-b<bounce-message>]\n"\
  32. "      [-x<level>] [message-ID ...]"
  33.     ARGC = Opts(Name,Usage,"Ab;c;VMS;lmnoOpqhiksw<t;a;d;r;eEx>",0)
  34.     if (Options["h"]) {
  35.     printf \
  36. "%s: list messages spooled in the MMDF mail queues.\n"\
  37. "%s\n"\
  38. "General options:\n"\
  39. "-h: Print this help.\n"\
  40. "-x<level>: Turn on debugging at level <level>, which should be a positive\n"\
  41. "    integer.\n"\
  42. "Formatting options:\n"\
  43. "-l: Print fields in longer (more detailed) format.  The full Message-ID is\n"\
  44. "    printed rather than just the variable part; source routes and the\n"\
  45. "    local domain are not removed from return and recipient addresses, and\n"\
  46. "    the destination host (if any) is prefixed to recipient addresses in !\n"\
  47. "    format.\n"\
  48. "-e: Print only message-IDs.\n"\
  49. "-p: Print only the full paths to the address and message files.  Only the\n"\
  50. "    path to the address file in the \"addr\" directory is printed.  Each\n"\
  51. "    pair of paths is separated from the next by a blank line.\n"\
  52. "-n: Do not print headers.\n"\
  53. "-o: Print in a format that has only one line per message,\n"\
  54. "    even if the message is in more than one queue.\n"\
  55. "-V: Verbose operation.  Print the size, time of queuing, and flags in\n"\
  56. "    addition to other fields.  The flags printed have these meanings:\n"\
  57. "    C (Citation): Warning/failure messages will include only a citation.\n"\
  58. "    R (Return): Message should be returned if it cannot be delivered.\n"\
  59. "    W (Warn): Warning messages should be sent if message is not delivered.\n"\
  60. "    L (Late): A warning message has been sent regarding this message.\n"\
  61. "-O: The age of each message (how long it has been queued) is printed\n"\
  62. "    instead of the date & time it was queued.  Used with -V.\n"\
  63. "-w<width>: Truncate displayed lines to at most <width> characters.\n"\
  64. "    The header is not truncated.  The default for <width> is one less than\n"\
  65. "    than the number of columns the terminal has.  If -w0 is given, lines\n"\
  66. "    are not truncated.\n"\
  67. "-A: Print only the contents of address files.  Each is preceded by its\n"\
  68. "    message ID and separated from the next by a blank line.\n"\
  69. "-q: Quiet operation; do not print any report.  Errors are still printed,\n"\
  70. "    and any message processing actions are still taken.\n"\
  71. "Message selection options:\n"\
  72. "-a<address>, -d<host>, -r<return-address>: For each message, each pending\n"\
  73. "    recipient address at the destination is matched against <address>,\n"\
  74. "    each pending destination host (if any) is matched against <host>, and\n"\
  75. "    the return address is matched against <return-address>, respectively.\n"\
  76. "    A match causes the message to be selected.  If a value that begins\n"\
  77. "    with a '!' is given, any message that matches the value is excluded\n"\
  78. "    (all others are selected).  If more than one option is given, all\n"\
  79. "    selecting options must result in selections and all excluding options\n"\
  80. "    must not result in exclusions in order for the message to be selected.\n"\
  81. "    The string to match against is a pattern in the style of egrep,\n"\
  82. "    implicitely anchored at the beginning and end.  Note that recipient\n"\
  83. "    addresses in queues other than \"local\" typically include a host\n"\
  84. "    part, and this must be included in the pattern.  The 'l' option\n"\
  85. "    affects the a and r options.\n"\
  86. "-c<channel[,channel]>: Select messages queued in the given channels.\n"\
  87. "-t<time>: Select messages queued <time> ago.  <time> is given as\n"\
  88. "    days[:hh[mm[ss]]].  A message will be selected if it was queued at\n"\
  89. "    least the given amount of time ago and no more than the given amount of\n"\
  90. "    time plus the time specification precision ago.  Examples:\n"\
  91. "    -t:01 selects messages at least 1 hour old but less than 2 hours old.\n"\
  92. "    -t:0130 selects messages 1.5 hours through 1.5 hours + 59 seconds old.\n"\
  93. "    If -<time> or +<time> is given, messages created less than or more than\n"\
  94. "    the given amount of time ago are selected.  Messages with no address\n"\
  95. "    file are also selected.\n"\
  96. "-M, -E: Select only messages that have no message (-M) or address (-E)\n"\
  97. "    file. -M and -E may be given together.\n"\
  98. "Message processing options (act upon the selected messages):\n"\
  99. "-i: Attempt immediate delivery of messages.\n"\
  100. "-k: Kill messages.  Check which messages are selected before using this.\n"\
  101. "-s: Resend messages to all addresses not yet successfully delivered to,\n"\
  102. "    and remove (kill) the original messages.  The original return address\n"\
  103. "    is used.  This is used for acting on modified MMDF setup, in which\n"\
  104. "    messages may need to be put in a different queue.\n"\
  105. "-S<pattern;string>: Like -s, except that for each recipient address, a\n"\
  106. "    single substitution is done, replacing the largest match against\n"\
  107. "    pattern with string.  Example: mail to certain users@armory.com is\n"\
  108. "    forwarded to user@aldebaran.armory.com.  However, aldebaran.armory.com\n"\
  109. "    is down and mail for those users is temporarily forwarded to a\n"\
  110. "    different host. To deal with any mail that built up before the change\n"\
  111. "    was made: -S'@aldebaran.armory.com$;@armory.com'\n"\
  112. "-b<bounce-message>: Send selected  messages back to the return address and\n"\
  113. "    dequeue them.  In the returned message, the message contents are\n"\
  114. "    prefixed with a report that they are undeliverable, with the reason\n"\
  115. "    given being <bounce-message>.\n",
  116.     Name,Usage
  117.     exit 0
  118.     }
  119.  
  120.     if ("x" in Options) {
  121.     Debug = Options["x"]
  122.     printf "Debugging is on at level %d\n",Debug > "/dev/stderr"
  123.     }
  124.     MMDFHome = "/usr/spool/mmdf/lock/home"
  125.     GoToHome = "cd " MMDFHome ";"
  126.  
  127.     # Convince awk that these are arrays
  128.     split("",Msg," ")
  129.     split("",Addr," ")
  130.     split("",Q," ")
  131.     if ("S" in Options && Options["S"] !~ ";") {
  132.     print "Bad recipient address substitution: no semicolon." \
  133.     > "/dev/stderr"
  134.     exit 1
  135.     }
  136.  
  137.     LimChan = "c" in Options
  138.     if (ARGC <= 1) {
  139.     # Put a : at the end so that if the last test fails gawk won't print
  140.     # a warning message due a nonzero exit value
  141.     if (LimChan) {
  142.         split(Options["c"],Chans,",")
  143.         for (i in Chans)
  144.         queueFiles = queueFiles " q." Chans[i] "/*"
  145.     }
  146.     else
  147.         queueFiles = " q.*/*"
  148.     Cmd = GoToHome \
  149. "for i in addr/* msg/* " queueFiles "; do [ -f \"$i\" ] && echo $i; done; :"
  150.     # Set Msg[], Addr[], and Q[] according to file names in the spool dirs
  151.     if (Debug)
  152.         printf "Getting all messages with: %s\n",Cmd > "/dev/stderr"
  153.     while ((Cmd | getline) == 1) {
  154.         split($0,Comp,"/")
  155.         RecordFilename(Comp[1],Comp[2],Msg,Addr,Q)
  156.     }
  157.     close(Cmd)
  158.     }
  159.     else {
  160.     MessageIDsGiven = 1
  161.     # Set QDirs[] to the dirs that address and data files are spooled in
  162.     FindQueues(QDirs)
  163.     QDirs["addr"]
  164.     QDirs["msg"]
  165.     NumMsgs = ARGC - 1
  166.  
  167.     for (i = 1; i < ARGC; i++)
  168.         if (i in ARGV) {
  169.         MessageID = ARGV[i]
  170.         # Message IDs can be given with or without the leading "msg."
  171.         if (MessageID !~ "^msg.")
  172.             MessageID = "msg." MessageID
  173.         for (Dir in QDirs) {
  174.             File = MMDFHome "/" Dir "/" MessageID
  175.             if ((getline < File) >= 0)
  176.             RecordFilename(Dir,MessageID,Msg,Addr,Q)
  177.             close(File)
  178.         }
  179.         if (!(MessageID in Q || MessageID in Msg||MessageID in Addr)) {
  180.             ErrMsg(MessageID ": No such message.")
  181.             NumMsgs--
  182.         }
  183.         }
  184.     if (!NumMsgs)
  185.         exit
  186.     }
  187.     if ("w" in Options)
  188.     HeadTailInit(-1,Options["w"] ? Options["w"] : -1,0,0)
  189.     else
  190.     HeadTailInit(-1)
  191.     MessageSizes(Msg)
  192.     exit PrintJobs(Msg,Addr,Q,"V" in Options,"l" in Options,"o" in Options,
  193.     LimChan, !("n" in Options || "e" in Options || "p" in Options || \
  194.     "q" in Options || "A" in Options),"e" in Options,"p" in Options,
  195.     "q" in Options,"A" in Options,Options["a"],Options["d"],Options["r"],
  196.     "M" in Options,"E" in Options,Options["t"],"i" in Options,"k" in Options,
  197.     "s" in Options || "S" in Options,Options["S"],Options["b"],MessageIDsGiven,
  198.     "O" in Options)
  199. }
  200.  
  201. # Makes the indices of Queues the set of queue directory names
  202. # Global variables: GoToHome should be a shell command to cd to
  203. # the MMDF spool home directory
  204. function FindQueues(Queues,  Cmd,QueueList) {
  205.     Cmd = GoToHome \
  206.     "for file in q.*; do [ -d $file ] && l=\"$l $file\"; done; echo $l"
  207.     if (Debug)
  208.     printf "Getting queue directory names with: %s\n",Cmd > "/dev/stderr"
  209.     Cmd | getline QueueList
  210.     close(Cmd)
  211.     if (Debug)
  212.     printf "Got queues: %s\n",QueueList > "/dev/stderr"
  213.     MakeSet(Queues,QueueList," ")
  214.     delete Queues[""]
  215. }
  216.  
  217. # Makes File an index of Msg[], Addr[], or Q[],
  218. # depending on whether Dir is "msg", "addr", or begins with "q.".
  219. # If Dir begins with "q.", Dir without the leading "q." is made the value
  220. # of or concatenated to the value of Q[File].
  221. function RecordFilename(Dir,File,Msg,Addr,Q) {
  222.     if (Dir == "addr") {
  223.     Addr[File]
  224.     if (Debug > 5)
  225.         printf "Adding %s to Addr[]\n",File > "/dev/stderr"
  226.     }
  227.     else if (Dir == "msg") {
  228.     Msg[File]
  229.     if (Debug > 5)
  230.         printf "Adding %s to Msg[]\n",File > "/dev/stderr"
  231.     }
  232.     else
  233.     if (Dir !~ /^q\../)
  234.         ErrMsg(Dir ": invalid directory name.")
  235.     else
  236.         if (File in Q) {
  237.         Q[File] = Q[File] " " Dir
  238.         if (Debug)
  239.             printf "Adding %s to Q[%s]\n",Dir,File > "/dev/stderr"
  240.         }
  241.         else {
  242.         Q[File] = Dir
  243.         if (Debug)
  244.             printf "Setting Q[%s] to %s\n",File,Dir > "/dev/stderr"
  245.         }
  246. }
  247.  
  248. # For each message-ID in Msg, sets Msg[message-ID] to the size of
  249. # the message file.
  250. # Global vars: MMDFHome should be the MMDF spool home directory
  251. function MessageSizes(Msg,  File,Cmd,TmpFile) {
  252.     if (IsEmpty(Msg))
  253.     return
  254.     # Do this first so it can all be done by one stat
  255.     for (File in Msg)
  256.     FileNames = FileNames " " File
  257.     FileNames = substr(FileNames,2)
  258.     # If more names than will fit on cmd line, put them in a file for stat
  259.     if (length(FileNames) > 4000) {
  260.     getline TmpFile < "/dev/pid"
  261.     close("/dev/pid")
  262.     TmpFile = "/tmp/#mmdfq." TmpFile
  263.     gsub(" ","\n",FileNames)
  264.     print FileNames > TmpFile
  265.     close(TmpFile)
  266.     Cmd = "cd " MMDFHome "/msg; stat -nr -fns -c' ' < " TmpFile \
  267.     "; exec rm -f " TmpFile
  268.     }
  269.     else {
  270.     # Cut off msg. and add later since this all has to fit on a
  271.     # command line.
  272.     gsub("msg.","",FileNames)
  273.     # exit 0 for gawk
  274.     Cmd = "cd " MMDFHome "/msg; for file in " FileNames \
  275.     "; do echo msg.$file; done | stat -nr -fns -c' '; exit 0"
  276.     }
  277.     if (Debug > 4)
  278.     printf "Getting message file sizes with: %s\n",Cmd > "/dev/stderr"
  279.     while ((Cmd | getline) == 1) {
  280.     Msg[$1] = $2
  281.     if (Debug > 5)
  282.         printf "Size of %s is %s\n",$1,$2 > "/dev/stderr"
  283.     }
  284.     close(Cmd)
  285. }
  286.  
  287. # Reads address file AddrFile
  288. # Makes Queues[] the set of channels that the message has addresses
  289. # still pending delivery through.
  290. # Addrs[], indexed by channel name, is set to a comma-separated list of the
  291. # addresses awaiting delivery (those not yet successfully delivered to) by
  292. # each channel.
  293. # Info["date"] is set to the messages creation date in UNIX format.
  294. # Info["return_addr"] is set to the return address.
  295. # The index "late" is set if the message is late.
  296. # The indexes "c", "q", and "z" are set if the message has the flags
  297. # corresponding to those submit options set.  See the description below.
  298.  
  299. # Return value: 1 if a message should be selected, 0 if not, -1 on error
  300.  
  301. # Format of an address file:
  302. # <creation-date><late><flags>
  303. # <return-address>
  304. # <temp-ok> <mode> <queue> <host> <local>
  305. # where
  306. # Line 1:
  307. #   <creation-date> is the creation time of the message, as numeric epoch time.
  308. #   It is used to sort the queue so mail is delivered in the order submitted.
  309. #   <late> is '*' if warning message has been sent about this message, else 'm'
  310. #   <flags> is a numeric representation of a 16-bit flag value.  The submit
  311. #   option that turns on each bit is given after the bit-place value.
  312. #   1: (c) warning/failure messages should include only citation.  If not set,
  313. #      the entire message is returned.
  314. #   2: (q) Do not return mail.  If not set, mail is returned to sender if it
  315. #      hasn't been completely processed within failtime.
  316. #   4: (z) Do net send late warnings.  If not set, warnings should be sent to
  317. #      the return-mail address if the entire address list has not been
  318. #      processed within warntime.
  319. # Line 2:
  320. #   <return-address> is the sender address.
  321. # Lines 3+:
  322. #   <temp-ok> is '+' if address has been verified by receiving host, else '-'
  323. #   <mode> tells where to send message: to mailbox(m), tty(t), both(b),
  324. #      either(e), or processing completed(*)
  325. #   <queue> is the name of the channel this address has been queued for.
  326. #   <host> is the official domain name of hte receiving host.
  327. #   <local> is the recipient's address on the receiving host.
  328. # Example:
  329. # 756648151m0
  330. # filbo@armory.com
  331. # - * local, , spcecdt
  332. # - m smtp "sco.com" "belal@sco.com"
  333. function ReadAddrs(AddrFile,Queues,Addrs,Info,Long,
  334. AddrPat,NotAddrPat,HostPat,NotHostPat,
  335. Recip,DestHost,Channel,OldFS,Ret,Line,GotPat,ASelect,HSelect,Exclude,Select,
  336. dateLine,Message,Flags) {
  337.     split("",Info)    # empty this so flags can be set
  338.     Message = AddrFile
  339.     sub(".*/","",Message)
  340.     # Allow for commas in a field separator; make sure no tabs exist in fields
  341.     OldFS = FS
  342.     FS = "[ \t]*[ ,\t][ \t]*"
  343.     Ret = 0
  344.     # Get creation date line
  345.     if ((ret = (getline dateLine < AddrFile)) != 1) {
  346.     if (!ret)
  347.         ErrMsg("Empty address file: " AddrFile)
  348.     else if (ret == -1)
  349.         ErrMsg("Could not open address file (" ERRNO "):\n" AddrFile)
  350.     else
  351.         ErrMsg("Could not open address file (unknown getline error " ret \
  352.         "):\n" AddrFile)
  353.     Ret = -1
  354.     }
  355.     # Current input line is creation-date/late/flags line, which should
  356.     # look like this: 756648151m0
  357.     else if (dateLine !~ /^[0-9]+[m*]0*[0-7]$/) {
  358.     ErrMsg("Bad line 1 in address file for message " Message)
  359.     Ret = -1
  360.     }
  361.     else if ((getline Info["return_addr"] < AddrFile) != 1) {
  362.     ErrMsg("Address file for message " Message \
  363.     " has no return-address line")
  364.     Ret = -1
  365.     }
  366.     else {
  367.     Flags = dateLine
  368.     sub("[^0-9].*","",dateLine)    # Get rid of late and flags info
  369.     sub("^[0-9]+","",Flags)        # Get rid of timestamp
  370.     if (Debug)
  371.         printf "Date: %s  Flags: %s\n",dateLine,Flags > "/dev/stderr"
  372.     if (Flags ~ /\*/)
  373.         Info["late"]
  374.     Flags = substr(Flags,2)+0
  375.     if (Debug > 3)
  376.         printf "intflags = %d\n",Flags > "/dev/stderr"
  377.     if (Flags >= 4) {
  378.         Info["z"]
  379.         Flags -= 4
  380.         if (Debug > 3)
  381.         printf "bit 4, nowarn (z), is set\n" > "/dev/stderr"
  382.     }
  383.     if (Flags >= 2) {
  384.         Info["q"]
  385.         Flags -= 2
  386.         if (Debug > 3)
  387.         printf "bit 2, noreturn (q), is set\n" > "/dev/stderr"
  388.     }
  389.     if (Flags >= 1) {
  390.         Info["c"]
  391.         Flags -= 1
  392.         if (Debug > 3)
  393.         printf "bit 1, citeonly (c), is set\n" > "/dev/stderr"
  394.     }
  395.     Info["date"] = dateLine
  396.     if (GotPat = HostPat != "" || NotHostPat != "" || AddrPat != "" || \
  397.     NotAddrPat != "") {
  398.         HSelect = HostPat == ""
  399.         ASelect = AddrPat == ""
  400.     }
  401.     # Get recipient lines
  402.     Line = 2
  403.     while ((getline < AddrFile) == 1) {
  404.         Line++
  405.         # These lines have format: <temp-ok> <mode> <queue> <host> <local>
  406.         if ($1 != "+" && $1 != "-") {
  407.         ErrMsg("Bad field 1 (temp-ok field) on line " Line \
  408.         " of address file for message " Message ".\n"\
  409.         "Entire line is: " Uncontrol($0))
  410.         Ret = -1
  411.         break
  412.         }
  413.         if ($2 == "*")    # Delivery completed
  414.         continue
  415.         gsub("\"","")
  416.         Recip = $NF
  417.         DestHost = $(NF - 1)
  418.         if (!Long)
  419.         Recip = CanonShort(Recip)
  420.  
  421.         if (GotPat) {
  422.         HSelect = HSelect || DestHost ~ "^(" HostPat ")$"
  423.         ASelect = ASelect || Recip ~ "^(" AddrPat ")$"
  424.         Exclude = Exclude ||
  425.         NotHostPat != "" && DestHost ~ "^(" NotHostPat ")$" ||
  426.         NotAddrPat != "" && Recip ~ "^(" NotAddrPat ")$"
  427.         }
  428.  
  429.         if (DestHost != "" && (Long || $NF !~ "@"))
  430.         Recip = DestHost "!" Recip
  431.         Channel = $3
  432.         if (Channel in Queues)
  433.         Addrs[Channel] = Addrs[Channel] "," Recip
  434.         else {
  435.         Addrs[Channel] = Recip
  436.         Queues[Channel]
  437.         }
  438.     }
  439.     }
  440.     close(AddrFile)
  441.     if (!Ret && Line < 3) {
  442.     ErrMsg("No recipient lines in address file for message " Message)
  443.     Ret = -1
  444.     }
  445.     FS = OldFS
  446.     Select = (!GotPat || HSelect && ASelect && !Exclude)
  447.     if (!Select && Debug) {
  448.     printf "No match for recip addrs or hosts for message %s\n",
  449.     Message > "/dev/stderr"
  450.     }
  451.     return Ret ? Ret : Select
  452. }
  453.  
  454. # Convert a time spec of the form days[:hh[mm[ss]]] into a range of times,
  455. # in UNIX epoch form.  The time given is relative to RelTime.
  456. # The low (old) end of the time range is put in Range["lo"];
  457. # the high (new) end of the time range is put in Range["hi"].
  458. # The range starts the given amount of time ago and extends into the past
  459. # for an amount of time equal to the time precision given (one day, hour,
  460. # minute, or second).
  461. # If -<time> or +<time> is given, the behaviour is the same except that
  462. # the hi end becomes 2^31 or the lo end becomes 0, respectively.
  463. # On success, null is returned; on error, and error message.
  464. function TimeSpec2Time(Time,RelTime,Range,
  465. RangeMod,Elem,TimeLen,Mult,i,nElem,f,m) {
  466.     if (Time ~ /^[-+]/) {
  467.     RangeMod = substr(Time,1,1)
  468.     Time = substr(Time,2)
  469.     }
  470.     nElem = split(Time,Elem,":")
  471.     TimeLen = length(Elem[2])
  472.     if (nElem < 1 || nElem > 2 || Time !~ /^[0-9]*(:[0-9]+)?$/ || TimeLen > 6)
  473.     return "Invalid time format"
  474.     # Test for this separately so an explicit error message can be returned,
  475.     # since it is likely to be a common mistake
  476.     if (TimeLen % 2)
  477.     return "Invalid time format: odd number of digits after ':'"
  478.     RelTime -= Elem[1] * (Secs = 24*60*60)
  479.     split("86400,3600,60,1",Mult,",")
  480.     TimeLen /= 2    # how many units in time; will be 0..3
  481.     Time = Elem[2]
  482.     for (i = 1; i <= TimeLen; i++) {
  483.     f = substr(Time,i*2-1,2)
  484.     m = Mult[i+1]
  485.     RelTime -= f * m
  486.     if (Debug)
  487.         printf "%d * %d seconds\n",f,m > "/dev/stderr"
  488.     }
  489.     if (RangeMod == "+")
  490.     Range["lo"] = 0
  491.     else
  492.     Range["lo"] = RelTime - Mult[TimeLen+1] - 1
  493.     if (RangeMod == "-")
  494.     Range["hi"] = 2^31
  495.     else
  496.     Range["hi"] = RelTime
  497.     return ""
  498. }
  499.  
  500. # Pring mmdf job status.
  501. # MsgSizes[] contains an index for each message file found; the value is
  502. # the size of the message file.
  503. # Addr[] contains an index for each address file found in the addr directory.
  504. # Q[] contains an index for each address file found in any queue directory.
  505. # The value is a space-separated list of queue dirs the address file was in.
  506. # LimChan is true if specific queues are being processed.
  507. # The other args are output formatting & message selection options.
  508. function PrintJobs(MsgSizes,Addr,Q,Verbose,Long,OneLine,LimChan,Header,IDsOnly,
  509. PathsOnly,Quiet,AFOnly,AddrPat,HostPat,ReturnPat,OnlyNoMsg,OnlyNoAddr,Time,
  510. Deliver,Kill,Resubmit,Substitution,BounceMessage,MessageIDsGiven,printAge,
  511. PrintFile,Format,File,Queue,ReturnAddr,DestHost,QueueLine,AddrLine,Sep,
  512. NotAddrPat,NotHostPat,NotReturnPat,
  513. DestHosts,CreationDate,Range,Msg,RetAddrs,Recips,GoodAddrs,Recipient,CurTime) {
  514.     if (AddrPat ~ /^!/) {
  515.     NotAddrPat = substr(AddrPat,2)
  516.     AddrPat = ""
  517.     }
  518.     if (HostPat ~ /^!/) {
  519.     NotHostPat = substr(HostPat,2)
  520.     HostPat = ""
  521.     }
  522.     if (ReturnPat ~ /^!/) {
  523.     NotReturnPat = substr(ReturnPat,2)
  524.     ReturnPat = ""
  525.     }
  526.     CurTime = systime()    # Do this only once, for consistency
  527.     # Convince awk that Files is an array.
  528.     # Files is not in the function declaration because it fails under gawk.
  529.     split("",Files," ")
  530.     if (Time != "") {
  531.     if (Msg = TimeSpec2Time(Time,CurTime,Range)) {
  532.         ErrMsg(Msg)
  533.         return 1
  534.     }
  535.     if (Debug)
  536.         printf "Selecting messages queued %s .. %s\n",
  537.         strftime("%c",Range["lo"]),strftime("%c",Range["hi"]) \
  538.         > "/dev/stderr"
  539.     }
  540.     # Make Addr[] tell where an address file for every message that has one is.
  541.     FindAddrFiles(Addr,Q,LimChan)
  542.     # Ignore zero-length message files w/no address file left by checkaddr
  543.     # Don't do this if specific channels are being processed, because Addr
  544.     # will be emptied by FindAddrFiles(), so we would skip all zero-length
  545.     # message files.
  546.     if (!LimChan)
  547.     for (File in MsgSizes)
  548.         if (!MsgSizes[File] && !(File in Addr))
  549.         delete MsgSizes[File]
  550.     # Make Files contain all message-IDs found in any dir
  551.     if (LimChan)
  552.     CopySet(Addr,Files)
  553.     else
  554.     Union(Addr,MsgSizes,Files)
  555.     if (IsEmpty(Files)) {
  556.     ErrMsg("mmdfq: No messages spooled in MMDF queue.")
  557.     return 0
  558.     }
  559.  
  560.     Format = MakeFormat(Long,OneLine,Verbose,Header,printAge)
  561.     # If doing a short display, need the local mail name so that it can be
  562.     # removed from addresses.
  563.     if (!Long && (Err = GetMailName()) != "")
  564.     ErrMsg(Err)
  565.  
  566.     # For every message that has an address or message file...
  567.     for (File in Files) {
  568.     if (Debug)
  569.         printf "Processing message %s\n",File > "/dev/stderr"
  570.     # Emtpy Queues and Addrs
  571.     split("",Queues," ")
  572.     split("",Addrs," ")
  573.     split("",DestHosts," ")
  574.     if (File in Addr) {
  575.         # If only listing no-addr files, this one is not of interest
  576.         if (OnlyNoAddr && !OnlyNoMsg)
  577.         continue
  578.         GoodAddrs = ReadAddrs(MMDFHome "/" Addr[File] "/" File,Queues,
  579.         Addrs,Info,Long,AddrPat,NotAddrPat,HostPat,NotHostPat)
  580.         # If message IDs were given on the command line, don't quit
  581.         # even if there is an error reading the address files, because
  582.         # we may be intentionally killing messages with bad address files.
  583.         if (!MessageIDsGiven && GoodAddrs != 1)
  584.         continue
  585.         RetAddrs[File] = ReturnAddr = Info["return_addr"]
  586.         CreationDate = Info["date"]
  587.         if (Time != "" && \
  588.         !(Range["lo"] <= CreationDate && CreationDate <= Range["hi"])) {
  589.         if (Debug)
  590.             printf "Time not in range for %s\n",File > "/dev/stderr"
  591.         continue
  592.         }
  593.         if (printAge)
  594.         Date = sec2dhms2(CurTime-CreationDate)
  595.         else
  596.         Date = strftime("%y/%m/%d,%T",CreationDate)
  597.         delete Addr[File]
  598.     }
  599.     else {
  600.         if (Debug)
  601.         printf "No address file for %s\n",File > "/dev/stderr"
  602.         Queues["!ADDR"]
  603.         Addrs["!ADDR"] = ReturnAddr = Date = "!ADDR"
  604.     }
  605.     if (Long)
  606.         PrintFile = File
  607.     else {
  608.         ReturnAddr = CanonShort(ReturnAddr)
  609.         PrintFile = substr(File,5)    # Remove leading 'msg.' from message ID
  610.     }
  611.     if (ReturnPat != "" && (ReturnAddr !~ "^(" ReturnPat ")$") ||
  612.     NotReturnPat != "" && (ReturnAddr ~ "^(" NotReturnPat ")$")) {
  613.         if (Debug)
  614.         printf "Deselection of message %s based on return address\n",
  615.         File > "/dev/stderr"
  616.         continue
  617.     }
  618.     AddrLine = QueueLine = ""
  619.     for (Queue in Queues) {
  620.         QueueLine = QueueLine "," Queue
  621.         AddrLine = AddrLine "," Addrs[Queue]
  622.     }
  623.     AddrLine = substr(AddrLine,2)
  624.     if (File in MsgSizes) {
  625.         if (OnlyNoMsg && !OnlyNoAddr)
  626.         continue
  627.     }
  628.     else
  629.         MsgSizes[File] = "NO_MSG"
  630.     if (OnlyNoMsg && OnlyNoAddr &&
  631.     !(MsgSizes[File] == "NO_MSG" || "!ADDR" in Addrs))
  632.         continue
  633.     # Message selection completed
  634.     if (IDsOnly)
  635.         print File
  636.     else if (PathsOnly) {
  637.         printf Sep
  638.         if (!("!ADDR" in Addrs))
  639.         printf "%s/addr/%s\n",MMDFHome,File
  640.         if (MsgSizes[File] != "NO_MSG")
  641.         printf "%s/msg/%s\n",MMDFHome,File
  642.         Sep = "\n"
  643.     }
  644.     else if (AFOnly) {
  645.         printf Sep
  646.         print File
  647.         cat(MMDFHome "/addr/" File)
  648.         Sep = "\n"
  649.     }
  650.     else if (!Quiet) {
  651.         if (OneLine) {
  652.         split("",Queues)
  653.         Queues[substr(QueueLine,2)]
  654.         Addrs[substr(QueueLine,2)] = AddrLine
  655.         }
  656.         for (Queue in Queues)
  657.         PrintLine(Verbose,Format,Queue,PrintFile,MsgSizes[File],
  658.         Date,ReturnAddr,Addrs[Queue],Info)
  659.     }
  660.     Recips[File] = AddrLine
  661.     SelectedMsgs[File]
  662.     }
  663.     ProcMessages(Deliver,Kill,Resubmit,Substitution,SelectedMsgs,RetAddrs,
  664.     Recips,BounceMessage)
  665.     return 0
  666. }
  667.  
  668. # Returns a string containing as many elements from set Items as will fit
  669. # in a string of length MaxLen with each pair of elements separated by Sep.
  670. # The items are removed from Items.  Always returns at least one item,
  671. # unless there are no items in Items.
  672. function MakeMaxStr(Sep,Items,MaxLen,  Len,SepLen,S,ILen) {
  673.     for (I in Items) {
  674.     S = I
  675.     Len = length(S)
  676.     delete Items[I]
  677.     break
  678.     }
  679.     SepLen = length(Sep)
  680.     for (I in Items) {
  681.     if (Len + (ILen = length(I) + SepLen) <= MaxLen) {
  682.         S = S Sep I
  683.         Len += ILen
  684.         delete Items[I]
  685.     }
  686.     else
  687.         break
  688.     }
  689.     return S
  690. }
  691.  
  692. function ProcMessages(Deliver,Kill,Resubmit,Substitution,Messages,RetAddrs,
  693. Recips,BounceMessage,
  694. S,Msg,File,List,Items,RecipList,Pattern,i,RepText,Elem,num,Cmd) {
  695.     if (Deliver)
  696.     while ((S = MakeMaxStr(" ",Messages,2000)) != "")
  697.         system("exec /usr/mmdf/bin/deliver -w " S)
  698.     else if (Kill)
  699.     while ((S = MakeMaxStr(" */",Messages,200)) != "") {
  700.         system(GoToHome "exec rm */" S)
  701.         gsub(" ./"," ",S)
  702.         ErrMsg("Killed: " S)
  703.     }
  704.     else if (Resubmit || BounceMessage != "") {
  705.     # Requeue message (using execmail) for all addresses not yet delivered
  706.     # to.
  707.     for (Msg in Messages) {
  708.         if (!(Msg in RetAddrs)) {
  709.         ErrMsg("No address file for message " Msg)
  710.         delete Messages[Msg]
  711.         }
  712.         File = MMDFHome "/msg/" Msg
  713.         if ((getline < File) != 1) {
  714.         ErrMsg("No message file for message " Msg)
  715.         delete Messages[Msg]
  716.         }
  717.         close(File)
  718.     }
  719.  
  720.     # Move msg files first so they won't be delivered while being
  721.     # reinjected
  722.     CopySet(Messages,Items)
  723.     while ((S = MakeMaxStr(" ",Items,2000)) != "")
  724.         system("cd " MMDFHome "/msg; exec mv " S " ../tmp")
  725.  
  726.     # If something is to be substituted for...
  727.     if (Substitution ~ ".+;") {
  728.         i = index(Substitution,";")
  729.         Pattern = substr(Substitution,1,i-1)
  730.         RepText = substr(Substitution,i+1)
  731.     }
  732.     for (Msg in Messages) {
  733.         RecipList = Recips[Msg]
  734.         if (Pattern != "") {
  735.         num = split(RecipList,Elem," ")
  736.         oRecipList = RecipList
  737.         RecipList = ""
  738.         for (i = 1; i <= num; i++) {
  739.             sub(Pattern,RepText,Elem[i])
  740.             RecipList = RecipList " " Elem[i]
  741.         }
  742.         if (Debug)
  743.             printf \
  744.         "Substitution on recipient list finished.\nOld: %s\nNew: %s\n",
  745.             oRecipList,RecipList > "/dev/stderr"
  746.         }
  747.         if (BounceMessage != "") {
  748.         Cmd = "exec /usr/lib/mail/execmail -dv -f /dev/null"
  749.         }
  750.         else
  751.         system( \
  752.         sprintf("exec /usr/lib/mail/execmail -dv -f %s %s < %s/tmp/%s",
  753.         RetAddrs[Msg],RecipList,MMDFHome,Msg))
  754.     }
  755.     while ((S = MakeMaxStr(" */",Messages,200)) != "")
  756.         system(GoToHome "exec rm */" S)
  757.     }
  758. }
  759.  
  760. # Generate format string & print headers
  761. function MakeFormat(Long,OneLine,Verbose,Header,printAge,
  762. MLen,RLen,QueueHdr,Format) {
  763.     # MLen: Length of Message-ID field.
  764.     # RLen: Length of Return-Addr field.
  765.     MLen = Long ? 11 : 7
  766.     QueueHdr = (Verbose ? "Chan" : "Channel") (OneLine ? "s" : "")
  767.     Format = Verbose ? "%-5s %-" MLen "s %6s %-17s %-4s " : "%-8s %-" MLen "s "
  768.     if (COLUMNS) {
  769.     RLen = int((COLUMNS - length(sprintf(Format,"","","","","")))/2)
  770.     if (RLen < 0)
  771.         RLen = 1
  772.     }
  773.     else
  774.     RLen = 20
  775.     if (Debug)
  776.     printf "Width of return-address field is %d\n",RLen
  777.     Format = Format "%-" RLen "s %s"
  778.     if (Header)
  779.     if (Verbose)
  780.         printf Format "\n",QueueHdr,"Message","Size",
  781.         printAge ? "Age" : "Date&Time Queued",
  782.         "Flgs","Return-addr","Addressees"
  783.     else
  784.         printf Format "\n",QueueHdr,"Message","Return-addr","Addressees"
  785.     return Format
  786. }
  787.  
  788. # Check consistency, and set Addr[file] to the name of one of the queue
  789. # directories that the file can be found in
  790. function FindAddrFiles(Addr,Q,LimChan,  File,QueueDirs) {
  791.     for (File in Q)
  792.     if (!(File in Addr))
  793.         ErrMsg(\
  794.         File " has no link in addr but has links in queues: " Q[File])
  795.     if (LimChan)
  796.     # If processing specific channels, we only needed addr/* files
  797.     # for a consistency check, so empty array now
  798.     split("",Addr,"")
  799.     else
  800.     for (File in Addr) {
  801.         if (!(File in Q))
  802.         ErrMsg("addr/" File " has no link in any q.* directory.")
  803.         Addr[File] = "addr"
  804.     }
  805.     # For any file that has no link in Addr, use the first link found in a
  806.     # queue directory instead.
  807.     for (File in Q)
  808.     if (!(File in Addr)) {
  809.         split(Q[File],QueueDirs)
  810.         Addr[File] = QueueDirs[1]
  811.     }
  812. }
  813.  
  814. # Print a line describing a queued message
  815. # Verbose: true if size, date, and flags should be printed
  816. # Format: printf format string; varies depending on Verbose
  817. # Queue: which channel's info for this message is being printed
  818. # PrintFile: message-ID
  819. # Size: size of message contents
  820. # Date: time that message was queued
  821. # ReturnAddr: return address
  822. # AddrLine: recipients that would be delivered to via Queue
  823. # Info: Extra out-of-band info (flags, etc.) for message
  824. function PrintLine(Verbose,Format,Queue,PrintFile,Size,Date,
  825. ReturnAddr,AddrLine,Info,  Flags) {
  826.     if (Verbose) {
  827.     if (Queue == "!ADDR")
  828.         Flags = "-"
  829.     else {
  830.         if ("late" in Info)
  831.         Flags = "L"
  832.         if ("c" in Info)
  833.         Flags = Flags "C"
  834.         if (!("q" in Info))
  835.         Flags = Flags "R"
  836.         if (!("z" in Info))
  837.         Flags = Flags "W"
  838.     }
  839.     HeadPrint(sprintf(Format,Queue,PrintFile,Size,Date,Flags,ReturnAddr,
  840.     AddrLine))
  841.     }
  842.     else
  843.     HeadPrint(sprintf(Format,Queue,PrintFile,ReturnAddr,AddrLine))
  844. }
  845.  
  846. function ErrMsg(S) {
  847.     print S | "cat 1>&2"
  848. }
  849.  
  850. # 91/03/13 john h. dubois iii
  851. # Sets global var "machine" to uucp name,
  852. # Sets global "domname" to MMDF name for system,
  853. # and "locname" to name without hostname hidden if MLOCMACHINE is defined.
  854. # Returns null on success, error message on failure.
  855. function GetMailName(  mlname,mldomain,mlocmachine,proc,tailor) {
  856.     tailor = "/usr/mmdf/mmdftailor"
  857.     if ((getline < tailor) == -1)
  858.         return "Could not open " tailor "."
  859.     else do {
  860.         if ($1 == "MLNAME")
  861.             mlname = $2
  862.         else if ($1 == "MLDOMAIN")
  863.             mldomain = $2
  864.         else if ($1 == "MLOCMACHINE")
  865.             mlocmachine = $2
  866.     else
  867.         continue
  868.     if (mlname != "" && mldomain != "" && mlocmachine != "")
  869.         break
  870.     } while ((getline < tailor) == 1)
  871.     close(tailor)
  872.     if (mlname == "" || mldomain == "")
  873.     return "Could not get MLNAME or MLDOMAIN"
  874.     domname = mlname "." mldomain
  875.     if (mlocmachine != "")
  876.     locname = mlocmachine "." domname
  877.     return ""
  878. }
  879.  
  880. # Returns the part of Addr that comes after the last @ character in Addr,
  881. # if any.  If there is no @ character in Addr, returns a null string.
  882. function GetHostPart(Addr) {
  883.     if (!match(Addr,"@[^@]*$"))
  884.     return ""
  885.     return substr(Addr,RSTART+1)
  886. }
  887.  
  888. # Convert Addr to short canonical form: remove host part if it is the same
  889. # as the local or external mail name; remove source routes.
  890. function CanonShort(Addr) {
  891.     if (domname != "" && GetHostPart(Addr) == domname)
  892.     Addr = substr(Addr,1,length(Addr) - (length(domname)+1))
  893.     else if (locname != "" && GetHostPart(Addr) == locname)
  894.     Addr = substr(Addr,1,length(Addr) - (length(locname)+1))
  895.     sub("^@.*:","",Addr)
  896.     return Addr
  897. }
  898.  
  899. ### Begin set library
  900.  
  901. function Intersection(A,B,Inter,  Elem,Count) {
  902.     for (Elem in A)
  903.     if (Elem in B) {
  904.         Inter[Elem]
  905.         Count++
  906.     }
  907.     return Count
  908. }
  909.  
  910. function Union(A,B,Both,  Elem) {
  911.     for (Elem in A)
  912.     Both[Elem]
  913.     for (Elem in B)
  914.     Both[Elem]
  915. }
  916.  
  917. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  918. function SubtractSet(Minuend,Subtrahend,  Elem) {
  919.     for (Elem in Subtrahend)
  920.     delete Minuend[Elem]
  921. }
  922.  
  923. function CopySet(From,To,  Elem) {
  924.     for (Elem in From)
  925.     To[Elem]
  926. }
  927.  
  928. # Returns 1 if Set is empty, 0 if not.
  929. function IsEmpty(Set,  i) {
  930.     for (i in Set)
  931.     return 0
  932.     return 1
  933. }
  934.  
  935. # MakeSet: make a set from a list.
  936. # An index with the name of each element of the list
  937. # is created in the given array.
  938. # Input variables:
  939. # Elements is a string containing the list of elements.
  940. # Sep is the character that separates the elements of the list.
  941. # Output variables:
  942. # Set is the array.
  943. # Return value: the number of elements added to the set.
  944. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  945.     Num = split(Elements,Names,Sep)
  946.     for (i = 1; i <= Num; i++)
  947.     Set[Names[i]]
  948.     return Num
  949. }
  950. # Returns the number of elements in set Set
  951. function NumElem(Set,  elem,Num) {
  952.     for (elem in Set)
  953.     Num++
  954.     return Num
  955. }
  956.  
  957. # Remove all elements from Set
  958. function DeleteAll(Set,  i) {
  959.     for (i in Set)
  960.     delete Set[i]
  961. }
  962.  
  963. ### End set library
  964. ### Begin head-tail routines
  965.  
  966. # @(#) HeadTail.awk 96/05/09
  967. # 95/04/28 Added tail routines.
  968. # 96/05/09 Added all args to HeadTailInit()
  969.  
  970. # Turn on screen-bounded printing.
  971. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  972. # Sets the number of screen lines and rows to Lines and Rows.
  973. # If -1 is passed for either, turns off bounding in that dimension.
  974. # If either is not set or 0 is passed for it, its value is taken from the
  975. # environment, or if not set there, from terminfo, or if not set there, from
  976. # the defaults (24 and 80).
  977. # By default, the other functions in this library leave a "grace space" of
  978. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  979. # value, the line gap is set to it.
  980. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  981.     # tput will use values in environment, but we want to avoid running
  982.     # it if possible.
  983.     if (Cols > 0)
  984.     COLUMNS = Cols
  985.     else if (!Cols)
  986.     if ("COLUMNS" in ENVIRON)
  987.         COLUMNS = ENVIRON["COLUMNS"]
  988.     else {
  989.         Cmd = "exec tput cols"
  990.         Cmd | getline COLUMNS
  991.         close(Cmd)
  992.         if (COLUMNS == "")
  993.         COLUMNS = 80
  994.     }
  995.     if (Lines > 0)
  996.     LINES = Lines
  997.     else if (!Lines)
  998.     if ("LINES" in ENVIRON)
  999.         LINES = ENVIRON["LINES"]
  1000.     else {
  1001.         Cmd = "exec tput lines"
  1002.         Cmd | getline LINES
  1003.         close(Cmd)
  1004.         if (LINES == "")
  1005.         LINES = 24
  1006.     }
  1007.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  1008.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  1009. }
  1010.  
  1011. # Do screen-bound printing.
  1012. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.
  1013. # When TailFlush() is called, they are printed.
  1014. # If LINES = 0, all lines are printed immediately.
  1015. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1016. # it.
  1017. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  1018. # saves lines in TailLines[] from 1..LINES-LINEGAP
  1019. # Embedded newlines split the line into multiple lines; trailing newlines are
  1020. # stripped.  Tabs are expanded to spaces.
  1021. function TailPrint(Line) {
  1022.     if (!LINES)
  1023.     print Line
  1024.     else {
  1025.     if (++TailPtr > (LINES-LINEGAP))
  1026.         TailPtr = 1
  1027.     TailLines[TailPtr] = Line
  1028.     }
  1029. }
  1030.  
  1031. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  1032.     if (!LINES)
  1033.     return
  1034.     NumPrinted = 0
  1035.     PrintLines = LINES-LINEGAP
  1036.     # Since lines may contain multiple lines, we must create a buffer to be
  1037.     # printed by reading line buffer backwards.
  1038.     # Stop when we have copied enough lines, or if we wrap around to the end
  1039.     # and find that the entire line buffer was not used.
  1040.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  1041.     # Split line into individual lines, then process them last to first
  1042.     Num = split(TailLines[TailPtr],Lines,"\n")
  1043.     for (i = Num; i >= 1; i--) {
  1044.         Line = Lines[i]
  1045.         if (i == Num && Line == "")    # discard trailing newline
  1046.         continue
  1047.         # Put this line at the front of the print buffer
  1048.         if (COLUMNS)
  1049.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  1050.         else
  1051.         Buffer = Line "\n" Buffer
  1052.         if (++NumPrinted == PrintLines)
  1053.         break
  1054.     }
  1055.     if (!--TailPtr)    # Wrap pointer if neccessary
  1056.         TailPtr = PrintLines
  1057.     }
  1058.     printf "%s",Buffer
  1059. }
  1060.  
  1061. # Do screen-bound printing.
  1062. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  1063. # HeadPrint().  Otherwise returns 1.
  1064. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1065. # it.
  1066. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  1067. # Line should not include newlines.
  1068. function HeadPrint(Line) {
  1069.     # Check first, in case some calls of this function to not check return
  1070.     # value, and in case LINES is 1.
  1071.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  1072.     return 0
  1073.     if (COLUMNS)
  1074.     print substr(Line,1,COLUMNS - COLGAP)
  1075.     else
  1076.     print Line
  1077.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  1078.     return 0
  1079.     return 1
  1080. }
  1081.  
  1082. function ColPrint(Line) {
  1083.     if (COLUMNS)
  1084.     print substr(Line,1,COLUMNS - COLGAP)
  1085.     else
  1086.     print Line
  1087.     return 1
  1088. }
  1089.  
  1090. ### End head-tail routines
  1091. ### Begin copy,append,cat routines
  1092. # append: append file Source to file Dest.
  1093. # The final return value from the read is returned.
  1094. # It will be 0 if the file was read successfully; -1 if not.
  1095. function append(Source,Dest,  Line,ret) {
  1096.     while ((ret = (getline Line < Source)) == 1)
  1097.     print Line >> Dest
  1098.     close(Source)
  1099.     close(Dest)
  1100.     return ret
  1101. }
  1102.  
  1103. # copy: append file Source to file Dest.
  1104. # The final return value from the read is returned.
  1105. # It will be 0 if the file was read successfully; -1 if not.
  1106. function copy(Source,Dest,  Line,ret) {
  1107.     while ((ret = (getline Line < Source)) == 1)
  1108.     print Line > Dest
  1109.     close(Source)
  1110.     close(Dest)
  1111.     return ret
  1112. }
  1113.  
  1114. # cat: Print a file.
  1115. # The final return value from the read is returned.
  1116. # It will be 0 if the file was read successfully; -1 if not.
  1117. function cat(Source,  Line,ret) {
  1118.     while ((ret = (getline Line < Source)) == 1)
  1119.     print Line
  1120.     close(Source)
  1121.     return ret
  1122. }
  1123. ### End copy,append,cat routines
  1124. ### Begin UnControl routines
  1125.  
  1126. # @(#) uncontrol.awk 1.1 96/05/29
  1127. # 92/11/09 john h. dubois iii (john@armory.com)
  1128. # 96/05/29 Added octal-only conversion.
  1129.  
  1130. # Uncontrol(S): Convert control characters in S to symbolic form.
  1131. # Characters in S with values < 32 and with value 127 are converted to the form
  1132. # ^X.  Characters with value >= 128 are converted to the octal form \0nnn,
  1133. # where nnn is the octal value of the character.
  1134. # The resulting string is returned.
  1135. # If OctalOnly is true, octal numbers are used for all symbolic values instead
  1136. # of ^X.
  1137. # Global variables: UncTable[] and char2octal[].
  1138. function Uncontrol(S,OctalOnly,  i,len,Output) {
  1139.     len = length(S)
  1140.     Output = ""
  1141.     if (!("a" in UncTable))
  1142.     MakeUncontrolTable()
  1143.     for (i = 1; i <= len; i++)
  1144.     Output = Output \
  1145.     (OctalOnly ? char2octal[substr(S,i,1)] : UncTable[substr(S,i,1)])
  1146.     return Output
  1147. }
  1148.  
  1149. # MakeUncontrolTable: Make tables for use by Uncontrol().
  1150. # Global variables:
  1151. # UncTable[] is made into a character -> symbolic character lookup table
  1152. # with characters with values < 32 and with value 127 converted to the form
  1153. # ^X, and characters with value >= 128 are converted to the octal form \0nnn.
  1154. # char2octal[] is made into a similar table but with all non-printing chars
  1155. # in the form \0nnn.
  1156. function MakeUncontrolTable(  i,c) {
  1157.     for (i = 0; i < 32; i++) {
  1158.     UncTable[c = sprintf("%c",i)] = "^" sprintf("%c",i + 64)
  1159.     char2octal[c] = "\\" sprintf("%03o",i)
  1160.     }
  1161.     for (i = 32; i < 127; i++) {
  1162.     c = sprintf("%c",i)
  1163.     char2octal[c]  = UncTable[c] = sprintf("%c",i)
  1164.     }
  1165.     UncTable[c = sprintf("%c",127)] = "^?"
  1166.     char2octal[c] = "\\0177"
  1167.     for (i = 128; i < 256; i++) {
  1168.     UncTable[c = sprintf("%c",i)] = "\\" sprintf("%03o",i)
  1169.     char2octal[c] = "\\" sprintf("%03o",i)
  1170.     }
  1171. }
  1172.  
  1173. ### End UnControl routines
  1174. ### Start of ProcArgs library
  1175. # @(#) ProcArgs 1.11 96/12/08
  1176. # 92/02/29 john h. dubois iii (john@armory.com)
  1177. # 93/07/18 Added "#" arg type
  1178. # 93/09/26 Do not count -h against MinArgs
  1179. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1180. #          Removed meaning of "+" or "-" by itself.
  1181. # 94/03/08 Added & option and *()< option types.
  1182. # 94/04/02 Added NoRCopt to Opts()
  1183. # 94/06/11 Mark numeric variables as such.
  1184. # 94/07/08 Opts(): Do not require any args if h option is given.
  1185. # 95/01/22 Record options given more than once.  Record option num in argv.
  1186. # 95/06/08 Added ExclusiveOptions().
  1187. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1188. #          Expand $VARNAME at the start of its filenames.
  1189. #          Let varname=0 and -option- turn off an option.
  1190. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1191. #          of the vars should be searched for in the environment.
  1192. #          Check for duplicate rcfiles.
  1193. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1194. #          now return various negatives values on error, not just -1, and
  1195. #          Opts() may set Err to various positive values, not just 1.
  1196. #          Added AllowUnrecOpt.
  1197. # 96/05/23 Check type given for & option
  1198. # 96/06/15 Re-port to awk
  1199. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1200. #          used by other functions.
  1201. # 96/10/15 Added OptChars
  1202. # 96/11/01 Added exOpts arg to Opts()
  1203. # 96/11/16 Added ; type
  1204. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1205. # 96/12/27 Added CmdLineOpt()
  1206.  
  1207. # optlist is a string which contains all of the possible command line options.
  1208. # A character followed by certain characters indicates that the option takes
  1209. # an argument, with type as follows:
  1210. # :    String argument
  1211. # ;    Non-empty string argument
  1212. # *    Floating point argument
  1213. # (    Non-negative floating point argument
  1214. # )    Positive floating point argument
  1215. # #    Integer argument
  1216. # <    Non-negative integer argument
  1217. # >    Positive integer argument
  1218. # The only difference the type of argument makes is in the runtime argument
  1219. # error checking that is done.
  1220.  
  1221. # The & option is a special case used to get numeric options without the
  1222. # user having to give an option character.  It is shorthand for [-+.0-9].
  1223. # If & is included in optlist and an option string that begins with one of
  1224. # these characters is seen, the value given to "&" will include the first
  1225. # char of the option.  & must be followed by a type character other than ":"
  1226. # or ";".
  1227. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1228.  
  1229. # Strings in argv[] which begin with "-" or "+" are taken to be
  1230. # strings of options, except that a string which consists solely of "-"
  1231. # or "+" is taken to be a non-option string; like other non-option strings,
  1232. # it stops the scanning of argv and is left in argv[].
  1233. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1234. # If an option takes an argument, the argument may either immediately
  1235. # follow it or be given separately.
  1236. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1237. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1238. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1239. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1240. # parameter to explicitly set the option-specifier characters.
  1241.  
  1242. # If an option that does not take an argument is given,
  1243. # an index with its name is created in Options and its value is set to the
  1244. # number of times it occurs in argv[].
  1245.  
  1246. # If an option that does take an argument is given, an index with its name is
  1247. # created in Options and its value is set to the value of the argument given
  1248. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1249. # If an option that takes an argument is given more than once,
  1250. # Options[option-name,"count"] is incremented, and the value is assigned to
  1251. # the index (option-name,instance) where instance is 2 for the second occurance
  1252. # of the option, etc.
  1253. # In other words, the first time an option with a value is encountered, the
  1254. # value is assigned to an index consisting only of its name; for any further
  1255. # occurances of the option, the value index has an extra (count) dimension.
  1256.  
  1257. # The sequence number for each option found in argv[] is stored in
  1258. # Options[option-name,"num",instance], where instance is 1 for the first
  1259. # occurance of the option, etc.  The sequence number starts at 1 and is
  1260. # incremented for each option, both those that have a value and those that
  1261. # do not.  Options set from a config file have a value of 0 assigned to this.
  1262.  
  1263. # Options and their arguments are deleted from argv.
  1264. # Note that this means that there may be gaps left in the indices of argv[].
  1265. # If compress is nonzero, argv[] is packed by moving its elements so that
  1266. # they have contiguous integer indices starting with 0.
  1267. # Option processing will stop with the first unrecognized option, just as
  1268. # though -- was given except that unlike -- the unrecognized option will not be
  1269. # removed from ARGV[].  Normally, an error value is returned in this case.
  1270. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1271. # be found, so the number of remaining arguments is returned instead.
  1272. # If OptChars is not a null string, it is the set of characters that indicate
  1273. # that an argument is an option string if the string begins with one of the
  1274. # characters.  A string consisting solely of two of the same option-indicator
  1275. # characters stops the scanning of argv[].  The default is "-+".
  1276. # argv[0] is not examined.
  1277. # The number of arguments left in argc is returned.
  1278. # If an error occurs, the global string OptErr is set to an error message
  1279. # and a negative value is returned.
  1280. # Current error values:
  1281. # -1: option that required an argument did not get it.
  1282. # -2: argument of incorrect type supplied for an option.
  1283. # -3: unrecognized (invalid) option.
  1284. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1285. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1286. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1287. {
  1288. # ArgNum is the index of the argument being processed.
  1289. # ArgsLeft is the number of arguments left in argv.
  1290. # Arg is the argument being processed.
  1291. # ArgLen is the length of the argument being processed.
  1292. # ArgInd is the position of the character in Arg being processed.
  1293. # Option is the character in Arg being processed.
  1294. # Pos is the position in OptList of the option being processed.
  1295. # NumOpt is true if a numeric option may be given.
  1296.     ArgsLeft = argc
  1297.     NumOpt = index(OptList,"&")
  1298.     OptionNum = 0
  1299.     if (OptChars == "")
  1300.     OptChars = "-+"
  1301.     while (OptChars != "") {
  1302.     c = substr(OptChars,1,1)
  1303.     OptChars = substr(OptChars,2)
  1304.     OptCharSet[c]
  1305.     OptTerm[c c]
  1306.     }
  1307.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1308.     Arg = argv[ArgNum]
  1309.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1310.         break    # Not an option; quit
  1311.     if (Arg in OptTerm) {
  1312.         delete argv[ArgNum]
  1313.         ArgsLeft--
  1314.         break
  1315.     }
  1316.     ArgLen = length(Arg)
  1317.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1318.         Option = substr(Arg,ArgInd,1)
  1319.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1320.         # If this option is a numeric option, make its flag be & and
  1321.         # its option string flag position be the position of & in
  1322.         # the option string.
  1323.         Option = "&"
  1324.         Pos = NumOpt
  1325.         # Prefix Arg with a char so that ArgInd will point to the
  1326.         # first char of the numeric option.
  1327.         Arg = "&" Arg
  1328.         ArgLen++
  1329.         }
  1330.         # Find position of flag in option string, to get its type (if any).
  1331.         # Disallow & as literal flag.
  1332.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1333.         if (AllowUnrecOpt) {
  1334.             Escape = 1
  1335.             break
  1336.         }
  1337.         else {
  1338.             OptErr = "Invalid option: " specGiven Option
  1339.             return -3
  1340.         }
  1341.         }
  1342.  
  1343.         # Find what the value of the option will be if it takes one.
  1344.         # NeedNextOpt is true if the option specifier is the last char of
  1345.         # this arg, which means that if the option requires a value it is
  1346.         # the next arg.
  1347.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1348.         if (GotValue = ArgNum + 1 < argc)
  1349.             Value = argv[ArgNum+1]
  1350.         }
  1351.         else {    # Value is included with option
  1352.         Value = substr(Arg,ArgInd + 1)
  1353.         GotValue = 1
  1354.         }
  1355.  
  1356.         if (HadValue = AssignVal(Option,Value,Options,
  1357.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1358.         specGiven)) {
  1359.         if (HadValue < 0)    # error occured
  1360.             return HadValue
  1361.         if (HadValue == 2)
  1362.             ArgInd++    # Account for the single-char value we used.
  1363.         else {
  1364.             if (NeedNextOpt) {    # option took next arg as value
  1365.             delete argv[++ArgNum]
  1366.             ArgsLeft--
  1367.             }
  1368.             break    # This option has been used up
  1369.         }
  1370.         }
  1371.     }
  1372.     if (Escape)
  1373.         break
  1374.     # Do not delete arg until after processing of it, so that if it is not
  1375.     # recognized it can be left in ARGV[].
  1376.     delete argv[ArgNum]
  1377.     ArgsLeft--
  1378.     }
  1379.     if (compress != 0) {
  1380.     dest = 1
  1381.     src = argc - ArgsLeft + 1
  1382.     for (count = ArgsLeft - 1; count; count--) {
  1383.         ARGV[dest] = ARGV[src]
  1384.         dest++
  1385.         src++
  1386.     }
  1387.     }
  1388.     return ArgsLeft
  1389. }
  1390.  
  1391. # Assignment to values in Options[] occurs only in this function.
  1392. # Option: Option specifier character.
  1393. # Value: Value to be assigned to option, if it takes a value.
  1394. # Options[]: Options array to return values in.
  1395. # ArgType: Argument type specifier character.
  1396. # GotValue: Whether any value is available to be assigned to this option.
  1397. # Name: Name of option being processed.
  1398. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1399. #     or 0 if it was given in a config file or in the environment.
  1400. # SingleOpt: true if the value (if any) that is available for this option was
  1401. #     given as part of the same command line arg as the option.  Used only for
  1402. #     options from the command line.
  1403. # specGiven is the option specifier character use, if any (e.g. - or +),
  1404. # for use in error messages.
  1405. # Global variables: OptErr
  1406. # Return value: negative value on error, 0 if option did not require an
  1407. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1408. # the arg.
  1409. # Current error values:
  1410. # -1: Option that required an argument did not get it.
  1411. # -2: Value of incorrect type supplied for option.
  1412. # -3: Bad type given for option &
  1413. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1414. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1415.     # If option takes a value...    [
  1416.     NumTypes = "*()#<>]"
  1417.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1418.     OptErr = "Bad type given for & option"
  1419.     return -3
  1420.     }
  1421.  
  1422.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1423.     if (!GotValue) {
  1424.         if (Name != "")
  1425.         OptErr = "Variable requires a value -- " Name
  1426.         else
  1427.         OptErr = "option requires an argument -- " Option
  1428.         return -1
  1429.     }
  1430.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1431.         OptErr = Err
  1432.         return -2
  1433.     }
  1434.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1435.     if (ArgType != ":" && ArgType != ";")
  1436.         Value += 0
  1437.     if ((Instance = ++Options[Option,"count"]) > 1)
  1438.         Options[Option,Instance] = Value
  1439.     else
  1440.         Options[Option] = Value
  1441.     }
  1442.     # If this is an environ or rcfile assignment & it was given a value...
  1443.     else if (!OptionNum && Value != "") {
  1444.     UsedValue = 1
  1445.     # If the value is "0" or "-" and this is the first instance of it,
  1446.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1447.     # turn off an option (for the simple "Option in Options" test) in such
  1448.     # a way that it cannot be turned on in a later file.
  1449.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1450.         Instance = 1
  1451.     else
  1452.         Instance = ++Options[Option]
  1453.     # Save the value even though this is a flag
  1454.     Options[Option,Instance] = Value
  1455.     }
  1456.     # If this is a command line flag and has a - following it in the same arg,
  1457.     # it is being turned off.
  1458.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1459.     UsedValue = 2
  1460.     if (Option in Options)
  1461.         Instance = ++Options[Option]
  1462.     else
  1463.         Instance = 1
  1464.     Options[Option,Instance]
  1465.     }
  1466.     # If this is a flag assignment without a value, increment the count for the
  1467.     # flag unless it was turned off.  The indicator for a flag being turned off
  1468.     # is that the flag index has not been set in Options[] but it has an
  1469.     # instance count.
  1470.     else if (Option in Options || !((Option,1) in Options))
  1471.     # Increment number of times this flag seen; will inc null value to 1
  1472.     Instance = ++Options[Option]
  1473.     Options[Option,"num",Instance] = OptionNum
  1474.     return UsedValue
  1475. }
  1476.  
  1477. # Option is the option letter
  1478. # Value is the value being assigned
  1479. # Name is the var name of the option, if any
  1480. # ArgType is one of:
  1481. # :    String argument
  1482. # ;    Non-null string argument
  1483. # *    Floating point argument
  1484. # (    Non-negative floating point argument
  1485. # )    Positive floating point argument
  1486. # #    Integer argument
  1487. # <    Non-negative integer argument
  1488. # >    Positive integer argument
  1489. # specGiven is the option specifier character use, if any (e.g. - or +),
  1490. # for use in error messages.
  1491. # Returns null on success, err string on error
  1492. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1493.     if (ArgType == ":")
  1494.     return ""
  1495.     if (ArgType == ";") {
  1496.     if (Value == "")
  1497.         Err = "must be a non-empty string"
  1498.     }
  1499.     # A number begins with optional + or -, and is followed by a string of
  1500.     # digits or a decimal with digits before it, after it, or both
  1501.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1502.     Err = "must be a number"
  1503.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1504.     Err = "may not include a fraction"
  1505.     else if (ArgType ~ "[()<>]" && Value < 0)
  1506.     Err = "may not be negative"
  1507.     # (
  1508.     else if (ArgType ~ "[)>]" && Value == 0)
  1509.     Err = "must be a positive number"
  1510.     if (Err != "") {
  1511.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1512.     if (Name != "")
  1513.         return ErrStr "variable " substr(Name,1,1) " " Err
  1514.     else {
  1515.         if (Option == "&")
  1516.         Option = Value
  1517.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1518.     }
  1519.     }
  1520.     else
  1521.     return ""
  1522. }
  1523.  
  1524. # Note: only the above functions are needed by ProcArgs.
  1525. # The rest of these functions call ProcArgs() and also do other
  1526. # option-processing stuff.
  1527.  
  1528. # Opts: Process command line arguments.
  1529. # Opts processes command line arguments using ProcArgs()
  1530. # and checks for errors.  If an error occurs, a message is printed
  1531. # and the program is exited.
  1532. #
  1533. # Input variables:
  1534. # Name is the name of the program, for error messages.
  1535. # Usage is a usage message, for error messages.
  1536. # OptList the option description string, as used by ProcArgs().
  1537. # MinArgs is the minimum number of non-option arguments that this
  1538. # program should have, non including ARGV[0] and +h.
  1539. # If the program does not require any non-option arguments,
  1540. # MinArgs should be omitted or given as 0.
  1541. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1542. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1543. # by the value of the environment variable HOME.  If a filename begins with
  1544. # $, the part from the character after the $ up until (but not including)
  1545. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1546. # environment; if found its value will be substituted, if not the filename will
  1547. # be discarded.
  1548. # rcfiles are read in the order given.
  1549. # Values given in them will not override values given on the command line,
  1550. # and values given in later files will not override those set in earlier
  1551. # files, because AssignVal() will store each with a different instance index.
  1552. # The first instance of each variable, either on the command line or in an
  1553. # rcfile, will be stored with no instance index, and this is the value
  1554. # normally used by programs that call this function.
  1555. # VarNames is a comma-separated list of variable names to map to options,
  1556. # in the same order as the options are given in OptList.
  1557. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1558. # searched for in the environment.  If set to -1, all values will be searched
  1559. # for in the environment.  Values given in the environment will override
  1560. # those given in the rcfiles but not those given on the command line.
  1561. # NoRCopt, if given, is an additional letter option that if given on the
  1562. # command line prevents the rcfiles from being read.
  1563. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1564. # ExclusiveOptions() for a description of exOpts.
  1565. # Special options:
  1566. # If x is made an option and is given, some debugging info is output.
  1567. # h is assumed to be the help option.
  1568.  
  1569. # Global variables:
  1570. # The command line arguments are taken from ARGV[].
  1571. # The arguments that are option specifiers and values are removed from
  1572. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1573. # The number of elements in ARGV[] should be in ARGC.
  1574. # After processing, ARGC is set to the number of elements left in ARGV[].
  1575. # The option values are put in Options[].
  1576. # On error, Err is set to a positive integer value so it can be checked for in
  1577. # an END block.
  1578. # Return value: The number of elements left in ARGV is returned.
  1579. # Must keep OptErr global since it may be set by InitOpts().
  1580. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1581. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1582.     if (MinArgs == "")
  1583.     MinArgs = 0
  1584.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1585.     optChars)
  1586.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1587.     if (ArgsLeft >= 0) {
  1588.         OptErr = "Not enough arguments"
  1589.         Err = 4
  1590.     }
  1591.     else
  1592.         Err = -ArgsLeft
  1593.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1594.     Name,OptErr,Usage > "/dev/stderr"
  1595.     exit 1
  1596.     }
  1597.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1598.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1599.     {
  1600.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1601.     Err = -e
  1602.     exit 1
  1603.     }
  1604.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1605.     {
  1606.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1607.     Err = 1
  1608.     exit 1
  1609.     }
  1610.     return ArgsLeft
  1611. }
  1612.  
  1613. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1614. # <variable-name><assignment-char><value>.
  1615. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1616. # line and whitespace between the variable name and the assignment character)
  1617. # is stripped.  Lines that do not contain an assignment operator or which
  1618. # contain a null variable name are ignored, other than possibly being noted in
  1619. # the return value.  If more than one assignment is made to a variable, the
  1620. # first assignment is used.
  1621. # Input variables:
  1622. # File is the file to read.
  1623. # Comment is the line-comment character.  If it is found as the first non-
  1624. #     whitespace character on a line, the line is ignored.
  1625. # Assign is the assignment string.  The first instance of Assign on a line
  1626. #     separates the variable name from its value.
  1627. # If StripWhite is true, whitespace around the value (whitespace between the
  1628. #     assignment char and trailing whitespace on the line) is stripped.
  1629. # VarPat is a pattern that variable names must match.
  1630. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1631. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1632. #     a line; no assignment operator is needed.  These variables are set in
  1633. #     the output array with a null value.  Lines containing nothing but
  1634. #     whitespace are still ignored.
  1635. # Output variables:
  1636. # Values[] contains the assignments, with the indexes being the variable names
  1637. #     and the values being the assigned values.
  1638. # Lines[] contains the line number that each variable occured on.  A flag set
  1639. #     is record by giving it an index in Lines[] but not in Values[].
  1640. # Return value:
  1641. # If any errors occur, a string consisting of descriptions of the errors
  1642. # separated by newlines is returned.  In no case will the string start with a
  1643. # numeric value.  If no errors occur,  the number of lines read is returned.
  1644. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1645. FlagsOK,
  1646. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1647.     if (Comment != "")
  1648.     Comment = "^" Comment
  1649.     AssignLen = length(Assign)
  1650.     if (VarPat == "")
  1651.     VarPat = "."    # null varname not allowed
  1652.     while ((Status = (getline Line < File)) == 1) {
  1653.     LineNum++
  1654.     sub("^[ \t]+","",Line)
  1655.     if (Line == "")        # blank line
  1656.         continue
  1657.     if (Comment != "" && Line ~ Comment)
  1658.         continue
  1659.     if (Pos = index(Line,Assign)) {
  1660.         Var = substr(Line,1,Pos-1)
  1661.         Val = substr(Line,Pos+AssignLen)
  1662.         if (StripWhite) {
  1663.         sub("^[ \t]+","",Val)
  1664.         sub("[ \t]+$","",Val)
  1665.         }
  1666.     }
  1667.     else {
  1668.         Var = Line    # If no value, var is entire line
  1669.         Val = ""
  1670.     }
  1671.     if (!FlagsOK && Val == "") {
  1672.         Errs = Errs \
  1673.         sprintf("\nBad assignment on line %d of file %s: %s",
  1674.         LineNum,File,Line)
  1675.         continue
  1676.     }
  1677.     sub("[ \t]+$","",Var)
  1678.     if (Var !~ VarPat) {
  1679.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1680.         LineNum,File,Var)
  1681.         continue
  1682.     }
  1683.     if (!(Var in Lines)) {
  1684.         Lines[Var] = LineNum
  1685.         if (Pos)
  1686.         Values[Var] = Val
  1687.     }
  1688.     }
  1689.     if (Status)
  1690.     Errs = Errs "\nCould not read file " File
  1691.     close(File)
  1692.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1693. }
  1694.  
  1695. # Variables:
  1696. # Data is stored in Options[].
  1697. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1698. # Global vars:
  1699. # Sets OptErr.  Uses ENVIRON[].
  1700. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1701. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1702. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1703. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1704.     split("",filesRead,"")    # make awk know this is an array
  1705.     NumVars = split(VarNames,Vars,",")
  1706.     TypesInd = Ret = 0
  1707.     if (EnvSearch == -1)
  1708.     EnvSearch = NumVars
  1709.     for (i = 1; i <= NumVars; i++) {
  1710.     Var = Vars[i]
  1711.     CharOpt = substr(OptList,++TypesInd,1)
  1712.     if (CharOpt ~ "^[:;*()#<>&]$")
  1713.         CharOpt = substr(OptList,++TypesInd,1)
  1714.     Map[Var] = CharOpt
  1715.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1716.     # Do not overwrite entries from environment
  1717.     if (i <= EnvSearch && Var in ENVIRON &&
  1718.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1719.         return Err
  1720.     }
  1721.  
  1722.     numrcFiles = split(rcFiles,fNames,":")
  1723.     for (i = 1; i <= numrcFiles; i++) {
  1724.     rcFile = fNames[i]
  1725.     if (rcFile ~ "^~/")
  1726.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1727.     else if (rcFile ~ /^\$/) {
  1728.         rcFile = substr(rcFile,2)
  1729.         match(rcFile,"^[a-zA-Z0-9_]*")
  1730.         envvar = substr(rcFile,1,RLENGTH)
  1731.         if (envvar in ENVIRON)
  1732.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1733.         else
  1734.         continue
  1735.     }
  1736.     if (rcFile in filesRead)
  1737.         continue
  1738.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1739.     # may be the same
  1740.     filesRead[rcFile]
  1741.     if ("x" in Options)
  1742.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1743.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1744.     if (retStr > 0)
  1745.         READ_RCFILE = 1
  1746.     else if (ret != "") {
  1747.         OptErr = retStr
  1748.         Ret = -1
  1749.     }
  1750.     for (Var in Lines)
  1751.         if (Var in Map) {
  1752.         if ((Err = AssignVal(Map[Var],
  1753.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1754.         Var in Values,Var,0)) < 0)
  1755.             return Err
  1756.         }
  1757.         else {
  1758.         OptErr = sprintf(\
  1759.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1760.         Lines[Var],rcFile)
  1761.         Ret = -1
  1762.         }
  1763.     }
  1764.  
  1765.     if ("x" in Options)
  1766.     for (Var in Map)
  1767.         if (Map[Var] in Options)
  1768.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1769.         "/dev/stderr"
  1770.         else
  1771.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1772.     return Ret
  1773. }
  1774.  
  1775. # OptSets is a semicolon-separated list of sets of option sets.
  1776. # Within a list of option sets, the option sets are separated by commas.  For
  1777. # each set of sets, if any option in one of the sets is in Options[] AND any
  1778. # option in one of the other sets is in Options[], an error string is returned.
  1779. # If no conflicts are found, nothing is returned.
  1780. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1781. # the exclusions presented by the first set of sets (ab,def,g) if:
  1782. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1783. # (a or b is in Options[]) AND (g is in Options) OR
  1784. # (d, e, or f is in Options[]) AND (g is in Options)
  1785. # An error will be returned due to the exclusions presented by the second set
  1786. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1787. # todo: make options given on command line unset options given in config file
  1788. # todo: that they conflict with.
  1789. function ExclusiveOptions(OptSets,Options,
  1790. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1791. SetNum,OSetNum) {
  1792.     NumSetSets = split(OptSets,SetSets,";")
  1793.     # For each set of sets...
  1794.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1795.     # NumSets is the number of sets in this set of sets.
  1796.     NumSets = split(SetSets[SetSet],Sets,",")
  1797.     # For each set in a set of sets except the last...
  1798.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1799.         s1 = Sets[SetNum]
  1800.         L1 = length(s1)
  1801.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1802.         # If any of the options in this set was given, check whether
  1803.         # any of the options in the other sets was given.  Only check
  1804.         # later sets since earlier sets will have already been checked
  1805.         # against this set.
  1806.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1807.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1808.             s2 = Sets[OSetNum]
  1809.             L2 = length(s2)
  1810.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1811.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1812.                 ErrStr = ErrStr "\n"\
  1813.                 sprintf("Cannot give both %s and %s options.",
  1814.                 c1,c2)
  1815.             }
  1816.     }
  1817.     }
  1818.     if (ErrStr != "")
  1819.     return substr(ErrStr,2)
  1820.     return ""
  1821. }
  1822.  
  1823. # The value of each instance of option Opt that occurs in Options[] is made an
  1824. # index of Set[].
  1825. # The return value is the number of instances of Opt in Options.
  1826. function Opt2Set(Options,Opt,Set,  count) {
  1827.     if (!(Opt in Options))
  1828.     return 0
  1829.     Set[Options[Opt]]
  1830.     count = Options[Opt,"count"]
  1831.     for (; count > 1; count--)
  1832.     Set[Options[Opt,count]]
  1833.     return count
  1834. }
  1835.  
  1836. # The value of each instance of option Opt that occurs in Options[] that
  1837. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1838. # Other values are made indexes of Set[].
  1839. # The return value is the number of instances of Opt in Options.
  1840. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1841.     ret = Opt2Set(Options,Opt,aSet)
  1842.     for (value in aSet)
  1843.     if (substr(value,1,1) == "!")
  1844.         nSet[substr(value,2)]
  1845.     else
  1846.         Set[value]
  1847.     return ret
  1848. }
  1849.  
  1850. # Returns true if option Opt was given on the command line.
  1851. function CmdLineOpt(Options,Opt,  i) {
  1852.     for (i = 1; (Opt,"num",i) in Options; i++)
  1853.     if (Options[Opt,"num",i] != 0)
  1854.         return 1
  1855.     return 0
  1856. }
  1857. ### End of ProcArgs library
  1858. ### Begin timeperiod routines.
  1859. # These functions operate on periods of time.
  1860.  
  1861. # Converts Seconds to the form [[[[<days>d]<hours>h]<min>m]<sec>s]
  1862. function sec2dhms(Seconds,  Days,Hours,Minutes,Time) {
  1863.     Days = int(Seconds / 86400)
  1864.     Seconds %= 86400
  1865.     Hours = int(Seconds / 3600)
  1866.     Seconds %= 3600
  1867.     Minutes = int(Seconds / 60)
  1868.     Seconds %= 60
  1869.     if (Days)
  1870.     Time = Days "d"
  1871.     if (Time || Hours)
  1872.     Time = Time Hours "h"
  1873.     if (Time || Minutes)
  1874.     Time = Time Minutes "m"
  1875.     if (!Time || Seconds)
  1876.     Time = Time Seconds "s"
  1877.     return Time
  1878. }
  1879.  
  1880. # Converts Seconds to the form [[[<days>d]hh:]mm:]ss
  1881. function sec2dhms2(Seconds,  Days,Hours,Minutes,Time) {
  1882.     Days = int(Seconds / 86400)
  1883.     Seconds %= 86400
  1884.     Hours = int(Seconds / 3600)
  1885.     Seconds %= 3600
  1886.     Minutes = int(Seconds / 60)
  1887.     Seconds %= 60
  1888.     if (Days)
  1889.     Time = Days "d "
  1890.     if (Time || Hours)
  1891.     Time = Time sprintf("%02d",Hours) ":"
  1892.     if (Time || Minutes)
  1893.     Time = Time sprintf("%02d",Minutes) ":"
  1894.     Time = Time sprintf("%02d",Seconds)
  1895.     return Time
  1896. }
  1897.  
  1898. # Converts Seconds to the form [[<days>d]hh:]mm|m
  1899. function sec2dhm(Seconds,  Days,Hours,Minutes,Time) {
  1900.     Days = int(Seconds / 86400)
  1901.     Seconds %= 86400
  1902.     Hours = int(Seconds / 3600)
  1903.     Seconds %= 3600
  1904.     Minutes = int(Seconds / 60)
  1905.     if (Days)
  1906.     Time = Days "d "
  1907.     if (Time || Hours)
  1908.     Time = Time sprintf("%02d",Hours) ":"
  1909.     if (Time)
  1910.     Time = Time sprintf("%02d",Minutes)
  1911.     else
  1912.     Time = Minutes
  1913.     return Time
  1914. }
  1915.  
  1916. # Converts Seconds to the form hours:mm:ss
  1917. function sec2hms(Seconds,  Hours,Minutes) {
  1918.     Hours = int(Seconds / 3600)
  1919.     Seconds %= 3600
  1920.     Minutes = int(Seconds / 60)
  1921.     Seconds %= 60
  1922.     return sprintf("%d:%02d:%02d",Hours,Minutes,Seconds)
  1923. }
  1924.  
  1925. ### End timeperiod routines
  1926.